use crate::dto;
use crate::service::{
    Error, endpoint_peer::EndpointPeerService,
    endpoint_peer_wireguard_config::EndpointPeerWireguardConfigService,
    endpoint_wireguard_config::EndpointWireguardConfigService,
};
use std::sync::Arc;
use wanmesh_common::error::ResponseError;
use wanmesh_common::net::wireguard;

#[async_trait::async_trait]
pub trait EndpointPeerWireguardConfigManagerService: EndpointPeerWireguardConfigService {
    async fn get_config(
        &self,
        endpoint_peer_id: &str,
    ) -> Result<dto::endpoint_peer_wireguard_config::EndpointPeerWireguardConfig, Error>;

    async fn try_get_config(
        &self,
        endpoint_peer_id: &str,
    ) -> Result<Option<dto::endpoint_peer_wireguard_config::EndpointPeerWireguardConfig>, Error>;
}

pub struct DefaultEndpointPeerWireguardConfigManagerService {
    endpoint_wireguard_config_service: Arc<dyn EndpointWireguardConfigService>,
    endpoint_peer_service: Arc<dyn EndpointPeerService>,
    endpoint_peer_wireguard_config_service: Arc<dyn EndpointPeerWireguardConfigService>,
}

impl DefaultEndpointPeerWireguardConfigManagerService {
    pub fn new(
        endpoint_wireguard_config_service: Arc<dyn EndpointWireguardConfigService>,
        endpoint_peer_service: Arc<dyn EndpointPeerService>,
        endpoint_peer_wireguard_config_service: Arc<dyn EndpointPeerWireguardConfigService>,
    ) -> Self {
        Self {
            endpoint_wireguard_config_service,
            endpoint_peer_service,
            endpoint_peer_wireguard_config_service,
        }
    }
}

#[async_trait::async_trait]
impl EndpointPeerWireguardConfigService for DefaultEndpointPeerWireguardConfigManagerService {
    async fn add(
        &self,
        endpoint_peer_id: &str,
        params: &dto::core::endpoint_peer_wireguard_config::AddParams,
    ) -> Result<(), Error> {
        self.endpoint_peer_wireguard_config_service
            .add(endpoint_peer_id, params)
            .await
    }

    async fn get(
        &self,
        endpoint_peer_id: &str,
    ) -> Result<dto::core::endpoint_peer_wireguard_config::EndpointPeerWireguardConfig, Error> {
        self.endpoint_peer_wireguard_config_service
            .get(endpoint_peer_id)
            .await
    }

    async fn try_get(
        &self,
        endpoint_peer_id: &str,
    ) -> Result<Option<dto::core::endpoint_peer_wireguard_config::EndpointPeerWireguardConfig>, Error>
    {
        self.endpoint_peer_wireguard_config_service
            .try_get(endpoint_peer_id)
            .await
    }

    async fn update(
        &self,
        endpoint_peer_id: &str,
        params: &dto::core::endpoint_peer_wireguard_config::UpdateParams,
    ) -> Result<(), Error> {
        self.endpoint_peer_wireguard_config_service
            .update(endpoint_peer_id, params)
            .await
    }

    async fn delete(&self, endpoint_peer_id: &str) -> Result<(), Error> {
        self.endpoint_peer_wireguard_config_service
            .delete(endpoint_peer_id)
            .await?;
        Ok(())
    }

    async fn exists(
        &self,
        filter: &dto::core::endpoint_peer_wireguard_config::Filter,
    ) -> Result<bool, Error> {
        self.endpoint_peer_wireguard_config_service
            .exists(filter)
            .await
    }
}

#[async_trait::async_trait]
impl EndpointPeerWireguardConfigManagerService
    for DefaultEndpointPeerWireguardConfigManagerService
{
    async fn get_config(
        &self,
        endpoint_peer_id: &str,
    ) -> Result<dto::endpoint_peer_wireguard_config::EndpointPeerWireguardConfig, Error> {
        let config = self
            .try_get_config(endpoint_peer_id)
            .await?
            .ok_or(ResponseError::endpoint_peer_not_found(endpoint_peer_id))?;
        Ok(config)
    }

    async fn try_get_config(
        &self,
        endpoint_peer_id: &str,
    ) -> Result<Option<dto::endpoint_peer_wireguard_config::EndpointPeerWireguardConfig>, Error>
    {
        let peer = self.endpoint_peer_service.get(endpoint_peer_id).await?;

        let peer_wireguard_config = self
            .endpoint_wireguard_config_service
            .get(peer.inner.peer_endpoint_id.as_str())
            .await?;

        let config = match self.try_get(endpoint_peer_id).await? {
            None => return Ok(None),
            Some(v) => v,
        };

        let private_key =
            wireguard::key::PrivateKey::from(peer_wireguard_config.config.interface.private_key);
        let public_key = wireguard::key::PublicKey::from(&private_key);
        Ok(Some(
            dto::endpoint_peer_wireguard_config::EndpointPeerWireguardConfig {
                config: dto::endpoint_peer_wireguard_config::WireguardConfig {
                    peer: dto::endpoint_peer_wireguard_config::WireguardPeerConfig {
                        inner: config.config.peer,
                        public_key: public_key.to_bytes(),
                    },
                },
            },
        ))
    }
}
